#include "Tokeniser.h"

const string Tokeniser::KEYWORD_AT = " at ";
const string Tokeniser::KEYWORD_ON = " on ";
const string Tokeniser::KEYWORD_BY = " by ";
const string Tokeniser::KEYWORD_FROM = " from ";
const string Tokeniser::KEYWORD_TO = " to ";

const string Tokeniser::TYPE_TASK = " task ";
const string Tokeniser::TYPE_EVENT = " event ";

const string Tokeniser::TIME_NONE = "none";
const QTime Tokeniser::TIME_MIDNIGHT = QTime::fromString("0000", "hhmm");

const char* Tokeniser::DATE_FORMAT_ARMY = "((\\d{1,2})(\\d{1,2})(\\d{2}))";
const char* Tokeniser::DATE_FORMAT_SHORT1 = "((\\d{1,2}/(\\d{1,2})))";
const char* Tokeniser::DATE_FORMAT_SHORT2 = "((\\d{1,2})/(\\d{1,2})/(\\d{2}))";
const char* Tokeniser::DATE_FORMAT_DAY = "(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)";
const char* Tokeniser::DATE_TODAY = "today|tdy";
const char* Tokeniser::DATE_TOMORROW = "tomorrow|tml|tmr";

const char* Tokeniser::TIME_FORMAT_12_HOUR1 = "(0?[1-9]|1[0-2])(?:[:]?(\\d{2}))?\\s?(am|pm)";
const char* Tokeniser::TIME_FORMAT_12_HOUR2 = "([1-9])(?:[:]?(\\d{2}))?\\s?(am|pm)";
const char* Tokeniser::TIME_FORMAT_24_HOUR_TYPE1 = "((?:\\d{2})(\\d{2}))?";
const char* Tokeniser::TIME_FORMAT_24_HOUR_TYPE2 = "((?:\\d{2}):(\\d{2}))?";

const char* Tokeniser::CMD_FORMAT_QUOTE1 = "\"[a-zA-Z0-9 /,.;]+\"$";
const char* Tokeniser::CMD_FORMAT_QUOTE2 = "\"[a-zA-Z0-9 /,.;]+\"[a-zA-Z0-9 /,.;]*$";

const char* Tokeniser::CMD_FORMAT_FLOATING = "add |add [a-zA-Z0-9_/.,' ]+$"; //must not accept all cases
const char* Tokeniser::CMD_FORMAT_DATE = "add [a-zA-Z0-9_/.,' ]+ (on|by|from) |add [a-zA-Z0-9_/.,' ]+ (on|by|from) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))";
const char* Tokeniser::CMD_FORMAT_DATETIME = "add [a-zA-Z0-9_/.,' ]+ (on|by|from) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})/(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) |add [a-zA-Z0-9_/.,' ]+ (on|by) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})/(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?)[blank]?"; //optional space
const char* Tokeniser::CMD_FORMAT_TIME = "add [a-zA-Z0-9_/.,' ]+ at |add [a-zA-Z0-9_/.,' ]+ at (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?)$";
const char* Tokeniser::CMD_FORMAT_TIMETIME = "add [a-zA-Z0-9_/.,' ]+ from (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pBm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) |add [a-zA-Z0-9_/.,' ]+ from (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pBm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) to |add [a-zA-Z0-9_/.,' ]+ from (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pBm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) to (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?)$";
const char* Tokeniser::CMD_FORMAT_DATEDATE = "add [a-zA-Z0-9_/.,' ]+ from ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) to |add [a-zA-Z0-9_/.,' ]+ from ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) to ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))$";
const char* Tokeniser::CMD_FORMAT_DATETIMETIME = "add [a-zA-Z0-9_/.,' ]+ on ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) from |add [a-zA-Z0-9_/.,' ]+ on ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) from (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) ||add [a-zA-Z0-9_/.,' ]+ on ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) from (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) to |add [a-zA-Z0-9_/.,' ]+ on ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) from (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) to (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?)$";
const char* Tokeniser::CMD_FORMAT_DATETIMEDATETIME = "add [a-zA-Z0-9_/.,' ]+ from ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) to |add [a-zA-Z0-9_/.,' ]+ from ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)) (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?) to ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?) (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?))$";
const char* Tokeniser::CMD_FORMAT_RELATIVEDAY = "add [a-zA-Z0-9_/.,' ]+ (today|tdy|tmr|tml|tomorrow) ?|add [a-zA-Z0-9_/.,' ]+ (today|tdy|tmr|tml|tomorrow) at |add [a-zA-Z0-9_/.,' ]+ (today|tdy|tmr|tml|tomorrow) at (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?)$";

const char* Tokeniser::CMD_FORMAT_EDITNAME = "edit |edit [1-9]+ |edit [1-9]+ n |edit [1-9]+ n [a-zA-Z0-9_/.,' ]+$";
const char* Tokeniser::CMD_FORMAT_EDITTIME = "edit |edit [1-9]+ |edit [1-9]+ (st|et) |edit [1-9]+ (st|et) (((0?[1-9]|1[0-2]):(\\d{2})?\\s?(am|pm))|((?:\\d{2})(?:[:]?)(\\d{2}))?)$";
const char* Tokeniser::CMD_FORMAT_EDITDATE = "edit |edit [1-9]+ |edit [1-9]+ (sd|ed) |edit [1-9]+ (sd|ed) ((today|tdy)|(tomorrow|tml|tmr)|((\\d{2})(\\d{2})(\\d{2}))|((\\d{2})/(\\d{2})(?:/(\\d{2}))?)|(Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:r(?:s(?:day)?)?)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?))$";

const char* Tokeniser::CMD_FORMAT_DELETE = "delete |delete [1-9]+$";
const char* Tokeniser::CMD_FORMAT_DISPLAY = "(display(?:[w|d|t|e]?))|displaydone";
const char* Tokeniser::CMD_FORMAT_SEARCH = "search |search [a-zA-Z0-9_/.,' ]+$";
const char* Tokeniser::CMD_FORMAT_REDO = "redo ";
const char* Tokeniser::CMD_FORMAT_UNDO = "undo ";
const char* Tokeniser::CMD_FORMAT_HELP = "help";
const char* Tokeniser::CMD_FORMAT_CLEAR = "clear";
const char* Tokeniser::CMD_FORMAT_EXIT = "exit";
const char* Tokeniser::CMD_FORMAT_DONE = "done |done [0-9]+";

bool Tokeniser::checkFormat(string str) {
    QRegExp dateFormat(CMD_FORMAT_DATE, Qt::CaseInsensitive);
    QRegExp dateTimeFormat(CMD_FORMAT_DATETIME, Qt::CaseInsensitive);
    QRegExp timeFormat(CMD_FORMAT_TIME, Qt::CaseInsensitive);
    QRegExp timeTimeFormat(CMD_FORMAT_TIMETIME, Qt::CaseInsensitive);
    QRegExp dateDateFormat(CMD_FORMAT_DATEDATE, Qt::CaseInsensitive);
    QRegExp dateTimeTimeFormat(CMD_FORMAT_DATETIMETIME, Qt::CaseInsensitive);
    QRegExp dateTimeDateTimeFormat(CMD_FORMAT_DATETIMEDATETIME, Qt::CaseInsensitive);
    QRegExp relativeDayFormat(CMD_FORMAT_RELATIVEDAY, Qt::CaseInsensitive);
    QRegExp floatingFormat(CMD_FORMAT_FLOATING);

    QRegExp editNameFormat(CMD_FORMAT_EDITNAME);
    QRegExp editTimeFormat(CMD_FORMAT_EDITTIME);
    QRegExp editDateFormat(CMD_FORMAT_EDITDATE);

    QRegExp deleteFormat(CMD_FORMAT_DELETE);
    QRegExp displayFormat(CMD_FORMAT_DISPLAY);
    QRegExp searchFormat(CMD_FORMAT_SEARCH);
    QRegExp redoFormat(CMD_FORMAT_REDO);
    QRegExp undoFormat(CMD_FORMAT_UNDO);
    QRegExp helpFormat(CMD_FORMAT_HELP);
	QRegExp clearFormat(CMD_FORMAT_CLEAR);
	QRegExp exitFormat(CMD_FORMAT_EXIT);
	QRegExp doneFormat(CMD_FORMAT_DONE);

    QString qstr = QString::fromStdString(str);

    if(isInString(str, " at ") || isInString(str, " from ") || isInString(str, " to ") || isInString(str, " on ") || isInString(str, " by ")) {
        if(dateFormat.exactMatch(qstr) || dateTimeFormat.exactMatch(qstr) || timeFormat.exactMatch(qstr) || timeTimeFormat.exactMatch(qstr) || dateDateFormat.exactMatch(qstr) || dateTimeTimeFormat.exactMatch(qstr) || dateTimeDateTimeFormat.exactMatch(qstr) || relativeDayFormat.exactMatch(qstr)) {
            return true;
        } else {
            return false;
        }
    } else {
		if(floatingFormat.exactMatch(qstr) || editNameFormat.exactMatch(qstr) || editTimeFormat.exactMatch(qstr) || editDateFormat.exactMatch(qstr) || deleteFormat.exactMatch(qstr) || displayFormat.exactMatch(qstr) || searchFormat.exactMatch(qstr) || redoFormat.exactMatch(qstr) || undoFormat.exactMatch(qstr) || helpFormat.exactMatch(qstr) || clearFormat.exactMatch(qstr) || exitFormat.exactMatch(qstr) || doneFormat.exactMatch(qstr)) {
            return true;
        }
        else {
            return false;
        }
    }
}


CommonUtils::COMMAND_TYPE Tokeniser::extractCommand(string& textLine) {
    string command;

    stringstream temp(textLine);
    temp >> command >> ws;
	textLine.clear();
    getline(temp, textLine);
	CommonUtils::toLower(command);
	if(command == CommonUtils::STRING_COMMAND_ADD){
		return CommonUtils::COMMAND_TYPE::COMMAND_ADD;
	} else if(command == CommonUtils::STRING_COMMAND_DELETE){
		return CommonUtils::COMMAND_TYPE::COMMAND_DELETE;
	} else if(command == CommonUtils::STRING_COMMAND_SET_DONE){
		return CommonUtils::COMMAND_TYPE::COMMAND_SET_DONE;
	} else if(command == CommonUtils::STRING_COMMAND_EDIT){
		return CommonUtils::COMMAND_TYPE::COMMAND_EDIT;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY;
	}else if(command == CommonUtils::STRING_COMMAND_SORT){
		return CommonUtils::COMMAND_TYPE::COMMAND_SORT;
	}else if(command == CommonUtils::STRING_COMMAND_SEARCH){
		return CommonUtils::COMMAND_TYPE::COMMAND_SEARCH;
	}else if(command == CommonUtils::STRING_COMMAND_UNDO){
		return CommonUtils::COMMAND_TYPE::COMMAND_UNDO;
	}else if(command == CommonUtils::STRING_COMMAND_REDO){
		return CommonUtils::COMMAND_TYPE::COMMAND_REDO;
	}else if (command == CommonUtils::STRING_COMMAND_DISPLAY_TASK){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_TASK;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_EVENT){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_EVENT;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_WEEK){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_WEEK;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_DAY){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_DAY;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_DAY_BEFORE){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_DAY_BEFORE;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_PAST_NOT_DONE){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_PAST_NOT_DONE;
	}else if (command == CommonUtils::STRING_COMMAND_CLEAR){
		return CommonUtils::COMMAND_TYPE::COMMAND_CLEAR;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_DONE){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_DONE;
	} else if (command == CommonUtils::STRING_COMMAND_DISPLAY_NOT_DONE){
		return CommonUtils::COMMAND_TYPE::COMMAND_DISPLAY_NOT_DONE;
	} else if (command == CommonUtils::STRING_COMMAND_EXIT){
		return CommonUtils::COMMAND_TYPE::COMMAND_EXIT;
	} else
		return CommonUtils::COMMAND_TYPE::COMMAND_INVALID;
}

string Tokeniser::extractKey(string& textLine) {
	QRegExp quote1Format(CMD_FORMAT_QUOTE1);
	QString qstr = QString::fromStdString(textLine);
	string name;
	unsigned pos1, pos2;

	if(quote1Format.exactMatch(qstr)) {
		pos1 = textLine.find_first_of("\"");
		if(pos1 == string::npos) {
			throw BotException(BotException::EXCEPTION_COMMAND_INVALID);
		} else {
			pos2 = textLine.find_first_of("\"", pos1 + 1);
		}
		if(pos2 == string::npos) {
			throw BotException(BotException::EXCEPTION_COMMAND_INVALID);
		} else {
			name = textLine.substr(pos1 + 1, pos2 - pos1 - 1);
		}
		return name;
	} else {
		return textLine;
	}
}

int Tokeniser::extractLineNum(string& textLine) {
    //need to check if there is num in string
    int lineNum;

    stringstream str(textLine);
    str >> lineNum >> ws;
    getline(str, textLine);

    return lineNum;
}

bool Tokeniser::isInString(string& textString, string keyword) {
	if (textString.find(keyword) != string::npos) {
		return true;
    } else {
		return false;
	}
}

string Tokeniser::extractName(string& textLine) {
	QRegExp quote2Format(CMD_FORMAT_QUOTE2);
	QString qstr = QString::fromStdString(textLine);
	string name;

	if(quote2Format.exactMatch(qstr)) {
		unsigned pos1 = textLine.find_first_of("\"");
		unsigned pos2 = textLine.find_first_of("\"", pos1 + 1);
		name = textLine.substr(pos1 + 1, pos2 - pos1 - 1);
		textLine = textLine.substr(pos2);
	} else {

    vector <string> v;
    v.push_back(" by ");
    v.push_back(" on ");
    v.push_back(" at ");
    v.push_back(" from ");
    v.push_back(" to ");

    int i = 0;
    unsigned pos;

    do{
        pos = textLine.find(v.at(i));
        ++i;
    } while(i < 5 && pos == std::string::npos);

    name = textLine.substr(0, pos);
    textLine = textLine.substr(pos); //" by <date>"
	}

    return name;
}

string Tokeniser::extractTypeToDisplay(string textLine) {
	string type;

	if(isInString(textLine, KEYWORD_BY)) {
		type = TYPE_TASK;
	} else {
		type = TYPE_EVENT;
	}
	return type;
}

int Tokeniser::checkNumOfKeyword(string &textLine) { //can be refactored
    int numOfKeyword = 0;

    if(isInString(textLine, " by ")) {
        ++numOfKeyword;
    }
    if(isInString(textLine, " on ")) {
        ++numOfKeyword;
    }
    if(isInString(textLine, " at ")) {
        ++numOfKeyword;
    }
    if(isInString(textLine, " from ")) {
        ++numOfKeyword;
    }
    if(isInString(textLine, " to ")) {
        ++numOfKeyword;
    }

    return numOfKeyword;
}



QDate Tokeniser::formatDate(string dateString) {

    QRegExp armyDateFormat(DATE_FORMAT_ARMY);
    QRegExp dayDateFormat(DATE_FORMAT_DAY, Qt::CaseInsensitive);
    QRegExp shortDateFormat2(DATE_FORMAT_SHORT2);
    QRegExp shortDateFormat1(DATE_FORMAT_SHORT1);
	QRegExp todayFormat(DATE_TODAY);
	QRegExp tomorrowFormat(DATE_TOMORROW);

    QString qstr = QString::fromStdString(dateString);

    QDate date;

    if(armyDateFormat.exactMatch(qstr)) {
        date = QDate::fromString(qstr, "dMyy");
        date = date.addYears(100);
	} else if(shortDateFormat2.exactMatch(qstr)) {
        date = QDate::fromString(qstr, "d/M/yy");
        date = date.addYears(100);
    } else if(shortDateFormat1.exactMatch(qstr)) {
        date = QDate::fromString(qstr, "d/M");
        int yearsToAdd = (QDate::currentDate().year())-date.year();
        date = date.addYears(yearsToAdd);
    } else if(dayDateFormat.exactMatch(qstr)) {
        date = QDate::fromString(qstr, "dddd");
		if(!date.isValid()) date = QDate::fromString(qstr, "ddd");
		int currentJulian = QDate::currentDate().toJulianDay();
		int entryJulian = date.toJulianDay();
		entryJulian = currentJulian + ((date.dayOfWeek()-QDate::currentDate().dayOfWeek()+7)%7);
		date = QDate::fromJulianDay(entryJulian);
	} else if(todayFormat.exactMatch(qstr)) {
		date = QDate::currentDate();
	} else if (tomorrowFormat.exactMatch(qstr)) {
		date = QDate::currentDate().addDays(1);
	}

    return date;
}

QTime Tokeniser::formatTime(string timeString) {

    QRegExp time12HourFormat1(TIME_FORMAT_12_HOUR1, Qt::CaseInsensitive);
    QRegExp time12HourFormat2(TIME_FORMAT_12_HOUR2, Qt::CaseInsensitive);
    QRegExp time24HourFormat1(TIME_FORMAT_24_HOUR_TYPE1);
    QRegExp time24HourFormat2(TIME_FORMAT_24_HOUR_TYPE2);

    QString qstr = QString::fromStdString(timeString);

    QTime time;

    if(time24HourFormat1.exactMatch(qstr)) {
        time = QTime::fromString(QString::fromStdString(timeString), "hhmm");
    } else if(time24HourFormat2.exactMatch(qstr)) {
        time = QTime::fromString(QString::fromStdString(timeString), "hh:mm");
    } else if(time12HourFormat1.exactMatch(qstr)) {
        time = QTime::fromString(QString::fromStdString(timeString), "hh:mmap");
    } else if(time12HourFormat2.exactMatch(qstr)) {
        time = QTime::fromString(QString::fromStdString(timeString), "h:mmap");
    } else {
        time = QTime::fromString(QString::fromStdString("0000"), "hhmm"); //exception
    }
    return time;
}

string Tokeniser::extractRelativeDay(string& textLine) {
	unsigned pos;
	string relativeDay;

	if(isInString(textLine, " today")) {
			pos = textLine.find(" today");
			relativeDay = "today";
		} else if(isInString(textLine, " tdy")) {
			pos = textLine.find(" tdy");
			relativeDay = "tdy";
		} else if(isInString(textLine, " tomorrow")) {
			pos = textLine.find(" tomorrow");
			relativeDay = "tomorrow";
		} else if(isInString(textLine, " tmr")) {
			pos = textLine.find(" tmr");
			relativeDay = "tmr";
		} else if(isInString(textLine, " tml")) {
			pos = textLine.find(" tml");
			relativeDay = "tml";
		}

		if(pos != string::npos) {
			textLine = textLine.substr(0, pos);
		} else {
			throw BotException(BotException::EXCEPTION_NAME_INVALID);
		}

		return relativeDay;
}

void Tokeniser::extractAndPackage(string& textLine, vector <string>& v) {
        string relativeDay;

        if(checkNumOfKeyword(textLine) == 0) {
            if(isInString(textLine, " today") || isInString(textLine, " tdy") || isInString(textLine, " tomorrow") || isInString(textLine, " tmr") || isInString(textLine, " tml")) {
                relativeDay = extractRelativeDay(textLine);
				v.push_back(extractKey(textLine));
				v.push_back(relativeDay);
            } else {
				v.push_back(extractKey(textLine));
			}
        } else {
            string tmp = textLine;
            string name = extractName(tmp);
            if(isInString(name, " today") || isInString(name, " tdy") || isInString(name, " tomorrow") || isInString(name, " tmr") || isInString(name, " tml")) {
                relativeDay = extractRelativeDay(name);
                v.push_back(name);

                v.push_back(relativeDay);
                stringstream temp(tmp);
                string str;
                temp >> str >> ws;
                getline(temp, textLine);
            }

            int num = checkNumOfKeyword(textLine);
            while(num > 0){
                v.push_back(extractName(textLine));
                stringstream temp(textLine);
                string str;
                temp >> str >> ws;
                getline(temp, textLine);

                --num;
            }
            if(!v.empty()) {
                v.push_back(textLine);
            }
        }
}

string Tokeniser::checkDateOrTime(string text) {
    QRegExp time12HourFormat1(TIME_FORMAT_12_HOUR1, Qt::CaseInsensitive);
    QRegExp time12HourFormat2(TIME_FORMAT_12_HOUR2, Qt::CaseInsensitive);
    QRegExp time24HourFormat1(TIME_FORMAT_24_HOUR_TYPE1);
    QRegExp time24HourFormat2(TIME_FORMAT_24_HOUR_TYPE2);
    QRegExp armyDateFormat(DATE_FORMAT_ARMY);
    QRegExp dayDateFormat(DATE_FORMAT_DAY, Qt::CaseInsensitive);
    QRegExp shortDateFormat2(DATE_FORMAT_SHORT2);
    QRegExp shortDateFormat1(DATE_FORMAT_SHORT1);
	QRegExp todayFormat(DATE_TODAY);
	QRegExp tomorrowFormat(DATE_TOMORROW);

    QString qstr = QString::fromStdString(text);

    if(time12HourFormat1.exactMatch(qstr) || time12HourFormat2.exactMatch(qstr) || time24HourFormat1.exactMatch(qstr) || time24HourFormat2.exactMatch(qstr)) {
        return "time";
	} else if(armyDateFormat.exactMatch(qstr) || dayDateFormat.exactMatch(qstr) || shortDateFormat2.exactMatch(qstr) || shortDateFormat1.exactMatch(qstr) || todayFormat.exactMatch(qstr) || tomorrowFormat.exactMatch(qstr)) {
        return "date";
    } else {
        return "INVALID";
    }
}

void Tokeniser::extractTask(vector<string> v, Entry* &newEntry) {
	if(v.size()==2){
			vector<string> temp=splitToWords(v[1]);
			if(temp.size()>2)
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			else {
				if(checkDateOrTime(temp[0]) == "time") {
					newEntry->setEndTime(formatTime(temp[0]));
				} else if(checkDateOrTime(temp[0]) == "date") {
					newEntry->setEndDate(formatDate(temp[0]));
				}

				if(temp.size()==2) {
					if(checkDateOrTime(temp[1]) == "time") {
						if(newEntry->getEndTime().isValid())
							throw BotException(BotException::EXCEPTION_TIME_INVALID);
						newEntry->setEndTime(formatTime(temp[1]));
					} else if(checkDateOrTime(temp[1]) == "date") {
						if(newEntry->getEndDate().isValid())
							throw BotException(BotException::EXCEPTION_DATE_INVALID);
						newEntry->setEndDate(formatDate(temp[1]));
					}
				}
			}
		} else if(v.size() == 3) {
			vector<string> temp=splitToWords(v[1]);//by xxxxx
			if(temp.size()>1)//should be only by date at time should not have by DT DT given at
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			if(checkDateOrTime(temp[0]) == "time") {
				newEntry->setEndTime(formatTime(temp[0]));
			} else if(checkDateOrTime(temp[0]) == "date") {
				newEntry->setEndDate(formatDate(temp[0]));
			}
			temp.clear();
			temp=splitToWords(v[2]);//at xxxxxx
			if(temp.size()>1)//should be only by date at time
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			if(checkDateOrTime(temp[0]) == "time") {
				if(newEntry->getEndTime().isValid())//already has valid time
							throw BotException(BotException::EXCEPTION_TIME_INVALID);
				newEntry->setEndTime(formatTime(temp[0]));
			} else if(checkDateOrTime(temp[0]) == "date") {
				if(newEntry->getEndDate().isValid())//if already has valid date
							throw BotException(BotException::EXCEPTION_DATE_INVALID);
				newEntry->setEndDate(formatDate(temp[0]));
			}
		}//task should not have more than 3 arguments, name, date/time, date/time
		else throw BotException(BotException::EXCEPTION_DATE_INVALID);
}

void Tokeniser::extractEvent(vector<string> v, Entry* &newEntry) {
	if(v.size()==2){
			vector<string> temp=splitToWords(v[1]);
			if(temp.size()>2)
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			//on DT
			if(checkDateOrTime(temp[0]) == "time") {
				newEntry->setEndTime(formatTime(temp[0]));
			} else if(checkDateOrTime(temp[0]) == "date") {
				newEntry->setEndDate(formatDate(temp[0]));
			}

			if(temp.size()==2) {//on DT DT
				if(checkDateOrTime(temp[1]) == "time") {
					if(newEntry->getEndTime().isValid())
						throw BotException(BotException::EXCEPTION_TIME_INVALID);
					newEntry->setEndTime(formatTime(temp[1]));
				} else if(checkDateOrTime(temp[1]) == "date") {
					if(newEntry->getEndDate().isValid())
						throw BotException(BotException::EXCEPTION_DATE_INVALID);
					newEntry->setEndDate(formatDate(temp[1]));
				}
			}
			
			newEntry->setStartDate(newEntry->getEndDate());
			newEntry->setStartTime(newEntry->getEndTime());
		} else if(v.size()==3) {
			//from datetime to datetime or relative day at time
			if(checkDateOrTime(v.at(1)) == "date" && checkDateOrTime(v.at(2)) == "time" ) {
				newEntry->setStartDate(formatDate(v.at(1)));
				newEntry->setEndDate(formatDate(v.at(1)));
				newEntry->setStartTime(formatTime(v.at(2)));
				newEntry->setEndTime(formatTime(v.at(2)));
			}

			vector<string> temp=splitToWords(v[1]);//from xxxxxxx
			if(checkDateOrTime(temp[0]) == "time") {
				newEntry->setStartTime(formatTime(temp[0]));
			} else if(checkDateOrTime(temp[0]) == "date") {
				newEntry->setStartDate(formatDate(temp[0]));
			}
			if(temp.size()==2)//from dt dt
				if(checkDateOrTime(temp[1]) == "time") {
					if(newEntry->getStartTime().isValid())
							throw BotException(BotException::EXCEPTION_TIME_INVALID);
					newEntry->setStartTime(formatTime(temp[1]));
				} else if(checkDateOrTime(temp[1]) == "date") {
					if(newEntry->getStartDate().isValid())
							throw BotException(BotException::EXCEPTION_DATE_INVALID);
					newEntry->setStartDate(formatDate(temp[1]));
				}
			temp.clear();
			temp=splitToWords(v[2]);//to xxxxxxx or at xxxxx
			if(checkDateOrTime(temp[0]) == "time") {
				if(!newEntry->getStartTime().isValid())
					newEntry->setStartTime(formatTime(temp[0]));
				newEntry->setEndTime(formatTime(temp[0]));
			} else if(checkDateOrTime(temp[0]) == "date") {
				newEntry->setEndDate(formatDate(temp[0]));
			}
			if(temp.size()==2){//to dt dt
				if(checkDateOrTime(temp[1]) == "time") {
					if(newEntry->getEndTime().isValid())//already has valid time
								throw BotException(BotException::EXCEPTION_TIME_INVALID);
					newEntry->setEndTime(formatTime(temp[1]));
				} else if(checkDateOrTime(temp[0]) == "date") {
					if(newEntry->getEndDate().isValid())//if already has valid date
								throw BotException(BotException::EXCEPTION_DATE_INVALID);
					newEntry->setEndDate(formatDate(temp[1]));
				}

			}//by this point, can have no start date/time or no end date/time
		} else if(v.size()==4) {//on Date from time to time
			vector<string> temp=splitToWords(v[1]);//on Date
			if(temp.size()>1)
				throw BotException(BotException::EXCEPTION_DATE_INVALID);
			if(checkDateOrTime(temp[0]) == "date") {//should only be date
				newEntry->setStartDate(formatDate(temp[0]));
				newEntry->setEndDate(formatDate(temp[0]));
			} else throw BotException(BotException::EXCEPTION_DATE_INVALID);
			temp.clear();
			temp=splitToWords(v[2]);// from time
			if(temp.size()>1)
				throw BotException(BotException::EXCEPTION_TIME_INVALID);
			if(checkDateOrTime(temp[0]) == "time") {//should only be time
				newEntry->setStartTime(formatTime(temp[0]));
			} else throw BotException(BotException::EXCEPTION_TIME_INVALID);
			temp.clear();
			temp=splitToWords(v[3]);//to time
			if(temp.size()>1)
				throw BotException(BotException::EXCEPTION_TIME_INVALID);
			if(checkDateOrTime(temp[0]) == "time") {//only time arguments accepted
				newEntry->setEndTime(formatTime(temp[0]));
			} else throw BotException(BotException::EXCEPTION_TIME_INVALID);
		}
}

Entry* Tokeniser::extractEntry(string& textLine) {
	vector <string> v;
	Entry* newEntry;
	if(isInString(textLine, KEYWORD_BY)) {
		newEntry = new Task;
	} else {
		newEntry = new Event;
	}
    extractAndPackage(textLine, v);

	if(newEntry->getEntryType()=="TASK"){
		extractTask(v, newEntry);
	}
	if(newEntry->getEntryType()=="EVENT") {
		extractEvent(v, newEntry);
	}
 
	newEntry->setName(v.at(0));

	validateEntry(newEntry);
	return newEntry;
}

vector<string> Tokeniser::splitToWords(string textLine) {
	vector<string> output;
	istringstream input(textLine);
	string temp;
	while(input >> temp) {
		output.push_back(temp);
		temp.clear();
	}
	return output;
}

string Tokeniser::convertDateTimeToString(string dateOrTime) {
    string convertedStr;

    if(checkDateOrTime(dateOrTime) == "time") {
        QTime time = formatTime(dateOrTime);
		convertedStr = time.toString(CommonUtils::FORMAT_TIME).toStdString();
    } else if(checkDateOrTime(dateOrTime) == "date") {
        QDate date = formatDate(dateOrTime);
		convertedStr = date.toString(CommonUtils::FORMAT_DATE).toStdString();
    }

    return convertedStr;
}

vector<string> Tokeniser::extractEntryToEdit(string& textLine) {
    vector<string> entryToEdit(5, "");
    string temp;

    stringstream str(textLine);
    str >> temp >> ws;

    getline(str, textLine);

    if(temp == "n") {
        entryToEdit[0] = textLine;
    } else if(temp == "st") {
        if(checkDateOrTime(textLine) == "time"){
            entryToEdit[1] = convertDateTimeToString(textLine);
        } else {
            throw BotException(BotException::EXCEPTION_TIME_INVALID);
        }
    } else if(temp == "sd") {
        if(checkDateOrTime(textLine) == "date"){
            entryToEdit[2] = convertDateTimeToString(textLine);
        } else {
            throw BotException(BotException::EXCEPTION_DATE_INVALID);
        }
    } else if(temp == "et") {
        if(checkDateOrTime(textLine) == "time"){
            entryToEdit[3] = convertDateTimeToString(textLine);
        } else {
            throw BotException(BotException::EXCEPTION_TIME_INVALID);
        }
    } else if(temp == "ed") {
        if(checkDateOrTime(textLine) == "date"){
            entryToEdit[4] = convertDateTimeToString(textLine);
        } else {
            throw BotException(BotException::EXCEPTION_DATE_INVALID);
        }
    } else {
        throw BotException(BotException::EXCEPTION_ATTRIBUTE_INVALID);
    }

    return entryToEdit;
}
void Tokeniser::validateEntry(Entry* newEntry) {
	if(newEntry->getStartDate().isValid() && (!newEntry->getStartTime().isValid())) {//valid date, invalid time
		newEntry->setStartTime(TIME_MIDNIGHT);
	}
	if((!newEntry->getStartDate().isValid()) && (newEntry->getStartTime().isValid())) {//valid time invalid date
		newEntry->setStartDate(QDate::currentDate());
	}
	if(newEntry->getEndDate().isValid() && (!newEntry->getEndTime().isValid())) {//valid date, invalid time
		newEntry->setEndTime(TIME_MIDNIGHT);
	}
	if((!newEntry->getEndDate().isValid()) && (newEntry->getEndTime().isValid())) {//valid time invalid date
		newEntry->setEndDate(QDate::currentDate());
	}
	if(newEntry->getStartDate().isValid() && newEntry->getStartTime().isValid() && newEntry->getEndDate().isValid() && newEntry->getEndTime().isValid()) {//all valid, then check if start is after end
		if(QDateTime(newEntry->getStartDate(),newEntry->getStartTime()) > QDateTime(newEntry->getEndDate(),newEntry->getEndTime())) {
			throw BotException(BotException::EXCEPTION_START_END_CONFLICT);
		}
	}
}